package cn.lg.soar.cloud.core.config;

import cn.lg.soar.common.data.dict.DictItem;
import cn.lg.soar.common.data.dict.IDict;
import cn.lg.soar.common.util.data.DataUtil;
import cn.lg.soar.common.util.data.RandomUtil;
import cn.lg.soar.core.api.IResourceApi;
import cn.lg.soar.core.dto.SysDict;
import cn.lg.soar.core.util.InnerSerializeUtils;
import cn.lg.soar.mvc.controller.ResourceApiController;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.client.RestTemplate;

import javax.annotation.PostConstruct;
import java.net.URI;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author luguoxiang 469599794@qq.com
 * @Date: 2023/10/1 15:44
 * 开源项目：https://gitee.com/lgx1992/lg-soar 求star！请给我star！请帮我点个star！
 */
public class ResourceApiImpl implements IResourceApi {

    private static final Logger LOGGER = LoggerFactory.getLogger(ResourceApiImpl.class);

    @Autowired
    private DiscoveryClient discoveryClient;
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private ResourceApiController local;
    @Autowired
    private CloudProperties properties;

    @Value("${spring.application.name}")
    private String applicationName;

    private Consumer<HttpHeaders> securityHandler;

    @PostConstruct
    public void setSecret() {
        if (DataUtil.isValuable(properties.getSecret())) {
            long time = properties.getExpire().toMillis();
            byte[] bytes = properties.getSecret().getBytes();
            securityHandler = (headers) -> {
                String random = RandomUtil.getString(32);
                String expire = String.valueOf(System.currentTimeMillis() + time);
                // 数据签名
                MessageDigest digest;
                try {
                    digest = MessageDigest.getInstance("MD5");
                } catch (NoSuchAlgorithmException e) {
                    throw new RuntimeException(e);
                }
                digest.update(bytes);
                digest.update(random.getBytes());
                digest.update(expire.getBytes());
                String sign = DataUtil.byteToHex(digest.digest());

                headers.add("security-random", random);
                headers.add("security-expire", expire);
                headers.add("security-sign", sign);
            };
        } else {
            securityHandler = (a) -> {};
        }
    }

    private static final Set<String> IGNORE_SERVICE_NAMES = new HashSet<>();
    static {
        IGNORE_SERVICE_NAMES.add("gateway");
        IGNORE_SERVICE_NAMES.add("seata-server");
    }

    @Override
    public Map<String, String> getAllPath() {
        List<String> services = discoveryClient.getServices();
        Map<String, String> map = new HashMap<>(services.size() << 7);
        completableFuture(service -> {
            return service.equals(applicationName) ? local.getAllPath() : remote(service, ALL_PATH, Map.class);
        }, map::putAll);
        return map;
    }

    @Override
    public IDict<Integer>[] getSysDict(@RequestParam("code") String code) {
        String path = SYS_DICT + "?code=" + code;
        List<String> services = discoveryClient.getServices();
        for (String service : services) {
            if (IGNORE_SERVICE_NAMES.contains(service)) {
                continue;
            }
            try {
                IDict<Integer>[] data = service.equals(applicationName) ? local.getSysDict(code) : remote(service, path, String.class, body -> {
                    JSONArray objects = InnerSerializeUtils.parseArray(body);
                    return objects.stream().map(x -> {
                        JSONObject d = (JSONObject) x;
                        DictItem<Integer> dictItem = new DictItem<>();
                        dictItem.setValue(d.getInteger("value"));
                        dictItem.setLabel(d.getString("label"));
                        return dictItem;
                    }).toArray(IDict[]::new);
                });
                if (data.length != 0) {
                    return data;
                }
            } catch (RuntimeException e) {
                LOGGER.error("获取 {} 服务枚举字典数据失败：{}", service, e.getMessage());
            }
        }
        return new IDict[0];
    }

    @Override
    public List<SysDict<Integer>> getAllSysDict() {
        List<SysDict<Integer>> list = new ArrayList<>(discoveryClient.getServices().size() << 6);
        completableFuture(service -> {
            return service.equals(applicationName) ? local.getAllSysDict() : remote(service, ALL_SYS_DICT, List.class, body -> {
                return (List) ((List<Map<String, Object>>) body).stream().map(x -> {
                    SysDict<Integer> sysDict = new SysDict<>();
                    sysDict.setName((String) x.get("name"));
                    sysDict.setCode((String) x.get("code"));
                    List<Map<String, Object>> dicts = (List) x.get("dicts");
                    IDict[] array = dicts.stream().map(d -> {
                        DictItem<Integer> dictItem = new DictItem<>();
                        dictItem.setValue((Integer) d.get("value"));
                        dictItem.setLabel((String) d.get("label"));
                        return dictItem;
                    }).toArray(IDict[]::new);
                    sysDict.setDicts(array);
                    return sysDict;
                }).collect(Collectors.toList());
            });
        }, list::addAll);

        return list.stream().distinct().collect(Collectors.toList());
    }

    @GetMapping(IResourceApi.ENUMS)
    @Override
    public Map<String, IDict<?>[]> getEnums(@RequestParam("names") String[] names) {
        String path = ENUMS + "?names=" + String.join(",", names);
        Map<String, IDict<?>[]> map = new HashMap<>(discoveryClient.getServices().size() << 7);
        completableFuture((service) -> {
            return service.equals(applicationName) ? local.getEnums(names) : remote(service, path, Map.class);
        }, map::putAll);
        return map;
    }

    /**
     * 请求获取数据
     * @param service
     * @param path
     * @param responseClass
     * @return
     * @param <T>
     */
    private <T, R>R remote(String service, String path, Class<T> responseClass, Function<T, R> function) {
        List<ServiceInstance> instances = discoveryClient.getInstances(service);
        ServiceInstance serviceInstance = instances.get(0);
        URI uri = serviceInstance.getUri();
        String url = uri.toString() + path;

        HttpHeaders headers = new HttpHeaders();
        securityHandler.accept(headers);
        HttpEntity<String> entity = new HttpEntity<>(headers);

        ResponseEntity<T> response = restTemplate.exchange(url, HttpMethod.GET, entity, responseClass);
        return function.apply(response.getBody());
    }

    private <T>T remote(String service, String path, Class<T> responseClass) {
        return remote(service, path, responseClass, x -> x);
    }

    private <U>void completableFuture(Function<String, U> function, Consumer<U> consumer) {
        List<String> services = discoveryClient.getServices();
        CompletableFuture<U>[] futures = services.stream()
                .filter(service -> !IGNORE_SERVICE_NAMES.contains(service))
                .map(service -> CompletableFuture.supplyAsync(() -> function.apply(service)))
                .toArray(CompletableFuture[]::new);
        try {
            CompletableFuture.allOf(futures).get();
            for (CompletableFuture<U> future : futures) {
                consumer.accept(future.get());
            }
        } catch (InterruptedException | ExecutionException e) {
            throw new RuntimeException(e);
        }
    }
}
